package alibaba.client;

import static alibaba.shared.Utils.CHECK_QUERY_STR;
import static alibaba.shared.Utils.CLIENT_PRINTING_PREFIX;
import static alibaba.shared.Utils.SIGNATURE_QUERY_STR;
import static alibaba.shared.Utils.SNAPSHOT_QUERY_PREFIX_STR;
import static alibaba.shared.Utils.getFileHash;
import static alibaba.shared.Utils.readFileIntoBuf;
import static alibaba.shared.Utils.readInputIntoBuf;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import org.apache.commons.io.FilenameUtils;

import alibaba.shared.AlibabaException;
import alibaba.shared.Base64;

/**
 * The alibaba client.
 *
 * @author Anna
 *
 */
public class AlibabaClient {
	/**
	 * Back up directory.
	 *
	 * @param directoryName
	 * @throws AlibabaException
	 * @throws URISyntaxException
	 */
	public static void backupDir(String directoryName, String user) throws AlibabaException,
			URISyntaxException {
		File dir = new File(directoryName);
		if (!dir.isDirectory())
			throw new AlibabaException("The given dir name(" + directoryName
					+ ") is not a directory.");

		String[] filesNames = dir.list();
		if (filesNames.length == 0) {
			System.out.println(CLIENT_PRINTING_PREFIX
					+ "The directory is empty. No need for backup.");
		}
		for (String fileName : filesNames) {
			File file = new File(FilenameUtils.concat(dir.getAbsolutePath(), fileName));
			if (!file.isFile()) {
				continue;
			}
			backupFile(file.getAbsolutePath(), user);
		}
	}

	/**
	 * Back up a file.
	 *
	 * @param fileName
	 * @throws AlibabaException
	 * @throws URISyntaxException
	 */
	public static void backupFile(String fileName, String user) throws AlibabaException,
			URISyntaxException {
		try {
			// check that the file exist on the client side
			File file = new File(fileName);
			if (!file.exists()) {
				throw new AlibabaException("The file " + fileName
						+ " doesn't exist on your file system.");
			}
			if (file.isDirectory()) {
				throw new AlibabaException("The file " + fileName
						+ " is actually a directory. Do backup dir " +
								"for backing up a directory.");
			}

			String fileAbsolutePath = FilenameUtils.normalize(file.getAbsolutePath());
			// calculate signature of file
			String signature = Base64.encodeBytes(getFileHash(file), Base64.URL_SAFE);

			URL url = new URI("http", null, "localhost", 8080, Utils.toUnixNotation(
					fileAbsolutePath), SIGNATURE_QUERY_STR + "=" + signature, null).toURL();
			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			connection.setRequestProperty(alibaba.shared.Utils.AUTHORIZATION_STR, user);
			connection.setDoOutput(true);
			connection.setRequestMethod("PUT");
			connection.connect();

			switch (connection.getResponseCode()) {
			case HttpURLConnection.HTTP_OK:
				// store succeeded
				break;
			case HttpURLConnection.HTTP_INTERNAL_ERROR:
				throw new AlibabaException(
						"The Alibaba server responsed with an internal error (500)");
			case HttpURLConnection.HTTP_NOT_FOUND:
				System.out.println(CLIENT_PRINTING_PREFIX + "The file " + fileName
						+ " is not backed up on the server.");
				// store new file on server
				storeFile(fileAbsolutePath, user);
				break;
			default:
				throw new AlibabaException("The Alibaba server responsed unexpectedly");
			}
		} catch (FileNotFoundException e) {
			throw new AlibabaException(e);
		} catch (IOException e1) {
			throw new AlibabaException(e1);
		}
	}

	private static void storeFile(String fileName, String user) throws IOException,
			AlibabaException, URISyntaxException {
		URL url = new URI("http", null, "localhost", 8080, Utils.toUnixNotation(fileName), null,
				null).toURL();
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		connection.setRequestProperty(alibaba.shared.Utils.AUTHORIZATION_STR, user);

		connection.setDoOutput(true);
		connection.setRequestMethod("PUT");

		byte[] fileData = readFileIntoBuf(new File(fileName));

		OutputStream os = connection.getOutputStream();
		os.write(fileData);
		os.flush();
		connection.connect();

		switch (connection.getResponseCode()) {
		case HttpURLConnection.HTTP_OK:
			// store succeeded
			break;
		case HttpURLConnection.HTTP_INTERNAL_ERROR:
			throw new AlibabaException("The Alibaba server responsed with an internal error (500)");
		default:
			throw new AlibabaException("The Alibaba server responsed unexpectedly");
		}
	}

	/**
	 * List files indir.
	 *
	 * @param dirName
	 * @throws AlibabaException
	 * @throws URISyntaxException
	 */
	public static void listFilesCmd(String dirName, String user, String snapshotNumber) throws AlibabaException,
			URISyntaxException {
		String[] files = listFiles(dirName, user, snapshotNumber);
		System.out
				.println(CLIENT_PRINTING_PREFIX + "Listing files from Directory(" + dirName + ")");
		for (String fileName : files) {
			System.out.println(Utils.toUnixNotation(fileName));
		}
	}

	private static String[] listFiles(String directoryName, String user, String snapshotNumber) throws AlibabaException,
			URISyntaxException {
		String[] dirList = null;

		// This was written in the forum in order to distinguish between the
		// backup file request and list files request.
		String dir = "/";
		if (!directoryName.equals("/")) {
			dir = FilenameUtils.normalize(new File(directoryName).getAbsolutePath() + "/");
		}

		try {
			String snapshotQuery = (snapshotNumber == null) ? null:SNAPSHOT_QUERY_PREFIX_STR + "=" + snapshotNumber;
			URL url = new URI("http", null, "localhost", 8080, Utils.toUnixNotation(dir),
					snapshotQuery	, null).toURL();
			HttpURLConnection connection;
			connection = (HttpURLConnection) url.openConnection();
			connection.setRequestProperty(alibaba.shared.Utils.AUTHORIZATION_STR, user);
			connection.setRequestMethod("GET");

			switch (connection.getResponseCode()) {
			case HttpURLConnection.HTTP_OK:
				byte[] data = readInputIntoBuf(connection.getInputStream());
				dirList = (new String(data)).split("\n");
				for (String fileName : dirList) {
					fileName = fileName.trim();
				}
				// server returns an empty line, whenthereis no fileinthe
				// directory.
				if (new String(data).trim().equals("")) {
					dirList = new String[0];
				}
				break;
			case HttpURLConnection.HTTP_INTERNAL_ERROR:
				throw new AlibabaException(
						"The Alibaba server responsed with an internal error (500)");
			default:
				throw new AlibabaException("The Alibaba server responsed unexpectedly");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return dirList;
	}

	/**
	 * Check and validate filesin thedirectory.
	 *
	 * @param directoryName
	 * @throws AlibabaException
	 * @throws URISyntaxException
	 */
	public static void checkFilesCmd(String directoryName, String user) throws AlibabaException,
			URISyntaxException {
		try {
			URL url = new URI("http", null, "localhost", 8080, Utils.toUnixNotation(Utils
					.toUnixNotation(directoryName)), CHECK_QUERY_STR, null).toURL();
			HttpURLConnection connection;
			connection = (HttpURLConnection) url.openConnection();
			connection.setRequestProperty(alibaba.shared.Utils.AUTHORIZATION_STR, user);
			connection.setRequestMethod("GET");
			connection.connect();

			int rc = connection.getResponseCode();
			switch (rc) {
			case HttpURLConnection.HTTP_OK:
				// get the file from body
				byte[] data = readInputIntoBuf(connection.getInputStream());
				System.out.println(new String(data));
				break;
			case HttpURLConnection.HTTP_INTERNAL_ERROR:
				throw new AlibabaException(
						"The Alibaba server responsed with an internal error (500)");
			default:
				throw new AlibabaException("The Alibaba server responsed unexpectedly");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Restore directory.
	 *
	 * @param directoryName
	 * @throws AlibabaException
	 * @throws URISyntaxException
	 */
	public static void restoreDir(String directoryName, String user, String snapshotNumber) throws AlibabaException,
			URISyntaxException {
		String[] fileNames = listFiles(directoryName, user, snapshotNumber);
		for (String fileName : fileNames) {
			restoreFile(fileName, user, snapshotNumber);
		}
	}

	/**
	 * Restore file.
	 *
	 * @param fileName
	 * @throws AlibabaException
	 * @throws URISyntaxException
	 */
	public static void restoreFile(String fileName, String user, String snapshotNumber) throws AlibabaException,
			URISyntaxException {
		FileOutputStream fos = null;
		try {
			String snapshotQuery = (snapshotNumber == null) ? null:SNAPSHOT_QUERY_PREFIX_STR + "=" + snapshotNumber;
			URL url = new URI("http", null, "localhost", 8080, Utils.toUnixNotation(fileName),
						snapshotQuery, null).toURL();

			HttpURLConnection connection = (HttpURLConnection) url.openConnection();
			connection.setRequestMethod("GET");
			connection.setRequestProperty(alibaba.shared.Utils.AUTHORIZATION_STR, user);
			connection.connect();

			switch (connection.getResponseCode()) {
			case HttpURLConnection.HTTP_OK:
				// get the file from body
				byte[] data = readInputIntoBuf(connection.getInputStream());
				File file = new File(FilenameUtils.separatorsToSystem(fileName));
				file.getParentFile().mkdirs();
				fos = new FileOutputStream(file);
				fos.write(data);
				System.out.println(CLIENT_PRINTING_PREFIX + "The client recieved file (" + fileName
						+ ")" + "from the server.");
				break;
			case HttpURLConnection.HTTP_INTERNAL_ERROR:
				throw new AlibabaException(
						"The Alibaba server responsed with an internal error (500)");
			case HttpURLConnection.HTTP_NOT_FOUND:
				System.out.println(CLIENT_PRINTING_PREFIX + "There is no such file (" + fileName
						+ ") on the server");
				break;
			default:
				throw new AlibabaException("The Alibaba server responsed unexpectedly");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally{
			if(fos!=null)
				try {fos.close();} catch (IOException e) {}
		}
	}

	public static void createSnapshot(String user, String snapshotNumber)
			throws URISyntaxException, AlibabaException {
		doSnapshotOperation(user, snapshotNumber, "PUT");
	}


	public static void deleteSnapshot(String user, String snapshotNumber)
			throws URISyntaxException, AlibabaException{
		doSnapshotOperation(user, snapshotNumber, "DELETE");
	}

	private static void doSnapshotOperation(String user, String snapshotNumber,String operation) throws URISyntaxException, AlibabaException {
		try {
			URL url = new URI("http", null, "localhost", 8080, "/", snapshotNumber, null).toURL();
			HttpURLConnection connection;
			connection = (HttpURLConnection) url.openConnection();
			connection.setRequestProperty(alibaba.shared.Utils.AUTHORIZATION_STR, user);
			connection.setRequestMethod(operation);
			connection.connect();

			int rc = connection.getResponseCode();
			switch (rc) {
			case HttpURLConnection.HTTP_OK:
				// snapshot operation success.
				break;
			case HttpURLConnection.HTTP_BAD_REQUEST:
				throw new AlibabaException("Bad snapshot number supplied.");
			case HttpURLConnection.HTTP_INTERNAL_ERROR:
				throw new AlibabaException(
						"The Alibaba server responsed with an internal error (500)");
			default:
				throw new AlibabaException("The Alibaba server responsed unexpectedly");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
